package cn.skyclass.net
{
	import cn.skyclass.beans.ICloneable;
	import cn.skyclass.controls.WaitingDialog;
	import cn.skyclass.events.RpcEvent;
	import cn.skyclass.events.WsEvent;
	
	import mx.rpc.AbstractOperation;
	import mx.rpc.AsyncToken;
	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.ResultEvent;
	import mx.rpc.soap.LoadEvent;
	import mx.rpc.soap.WebService;
	
	
	/**
	 * 使用 WebService 来进行远程调用。
	 * 
	 * 用于在服务器端和客户端之间进行传输数据的对象可以选择实现以下接口以自定义执行方式：
	 * 1，实现接口 cn.skyclass.beans.ICloneable 来允许对参数对象的内容进行格式化，格式化是对当前参数的副本进行格式化。
	 * 2，实现接口 cn.skyclass.net.IRpcBean 来定义对象内容的格式化方式，否则对象将不会被格式化。
	 * 3，实现接口 cn.skyclass.net.IRpcBean 来自定义调用返回结果的对象内容的组装方式。
	 * 
	 * 注1：WebService 对参数的值有一些特殊的要求，所以进行调用时请注意参数值的合法性。对所有的参数进行必要的格式化。
	 * 如，NaN 和值为 null 的 Date 对象都会导致调用失败。
	 * 
	 * 事件：
	 * WsEvent.RESULT : 远程调用结束时将发布此事件，事件对象中包含所有的结果信息。
	 * WsEvent.FAULT ：远程调用失败是将发布此事件，事件对象中将包含相应的错误信息。
	 * 
	 * 注2：事件 RoEvent.RESULT 与基类 RpcHelper 中的事件 RpcEvent.RPC_RESULT 是等同的，
	 * 他们包含的值以及发布的时机均相同，所以监听这两个事件的结果是等价的。
	 * 另外两个事件 RoEvent.FAULT 与 RpcEvent.RPC_FAULT 也类似。
	 */
	[Event(name="result", type="cn.skyclass.events.WsEvent")]
	[Event(name="fault", type="cn.skyclass.events.WsEvent")]
	public class WsHelper extends RpcHelper
	{
		/**
		 * 实例化一个新实例
		 * 
		 * @ wsdlOrServiceName : 根据此值加载其对应的 WebService
		 * 		此值可以是 WebService 的名称，如 UserService ；也可以是一个完整的 wsdl，
		 * 		如 http://localhost:8080/java-web-tester/services/UserService?wsdl
		 * @ resultBeanCla : 远程调用将返回的 bean 类型
		 */
		public function WsHelper(wsdlOrServiceName:String, resultBeanCla:Class=null){
			// get wsdl through WsPool if it is name
			if(wsdlOrServiceName.indexOf("/") < 0){
				wsdlOrServiceName = WsPool.getWsdl(wsdlOrServiceName);
			}
			
			this.wsdl = wsdlOrServiceName;
			this.resultBeanCla = resultBeanCla;
		}	
		
		protected var ws:WebService;
		protected var wsdl:String;
				
	
		/**
		 * 是否起用新的 WebService，如果此项为 true ，
		 * 将根据 ws 中的 wsdl 重新加载一个 WebService
		 */
		private var _startNewWebService:Boolean = false;
		public function get startNewWebService():Boolean{
			return this._startNewWebService;
		}
		public function set startNewWebService(value:Boolean):void{
			this._startNewWebService = value;
		}


		override protected function loadListResultHandler(e:ResultEvent):void{		
			super.loadListResultHandler(e);
			
			var rw:ResultWrapper = new ResultWrapper(e);			 
			var re:WsEvent= new WsEvent(WsEvent.RESULT);			
			re.beansCollection = rw.getBeanArray(this.resultBeanCla);
			re.resultEvent = e;
			dispatchRpcResultEvent(re);
		}		
			
		override protected function getBeanResultHandler(e:ResultEvent):void{		
			super.getBeanResultHandler(e);
			
			var rw:ResultWrapper = new ResultWrapper(e);			 
			var re:WsEvent = new WsEvent(WsEvent.RESULT);
			re.beanData = rw.getBeanObject(this.resultBeanCla);
			re.result = re.beanData as OperationResult;
			re.resultEvent = e;
			dispatchRpcResultEvent(re);
		}	
				
		override protected function executeResultHandler(e:ResultEvent):void{		
			super.executeResultHandler(e);
			
			var rw:ResultWrapper = new ResultWrapper(e);				 
			var re:WsEvent = new WsEvent(WsEvent.RESULT);
			re.beanData = rw.getBeanObject(this.resultBeanCla);
			re.result = re.beanData as OperationResult;
			re.resultEvent = e;
			
			if(re.result != null){
				re.result.originalArgs = this.operationArgs;
				this.endExecuteCall(re.result);
			}
			dispatchRpcResultEvent(re);
		}			
		
		
		override protected function callOperation(resultListener:Function, 
			operationName:String, args:Array):AsyncToken{
			super.callOperation(resultListener, operationName, args);		
			
			this.ws = WsPool.getInstance().getWebService(this.wsdl);		
			if(this.ws && !this.startNewWebService){
				return this.doCallOperation();
			}else{	
				trace("");			
				trace("----> start a new web service, wsdl:", this.wsdl);
				this.ws = new WebService();
				this.ws.wsdl = this.wsdl;
				this.ws.addEventListener(FaultEvent.FAULT, this.wsFaultHandler);
				this.ws.addEventListener(LoadEvent.LOAD, this.wsLoadHandler);
				this.ws.loadWSDL();
				return null;
			}	
		}		
		
		
		protected function wsLoadHandler(e:LoadEvent):void{
			var msg:String = "a web service loaded successfully, wsdl:" + this.ws.wsdl;
			trace(msg);
			
			this.ws.removeEventListener(FaultEvent.FAULT, this.wsFaultHandler);
			this.ws.removeEventListener(LoadEvent.LOAD, this.wsLoadHandler);			
			
			// add a new web service to ws pool
			if(!this.startNewWebService){
				WsPool.getInstance().putWebService(this.wsdl, this.ws);
			}
			
			this.doCallOperation();
		}
		
		protected function wsFaultHandler(e:FaultEvent):void{
			var msg:String = "a web service loaded failed, wsdl:" + this.ws.wsdl;
			trace(msg);
			
			this.ws.removeEventListener(FaultEvent.FAULT, this.wsFaultHandler);
			this.ws.removeEventListener(LoadEvent.LOAD, this.wsLoadHandler);			
			this.endFaildCall(null, e);
		}
		
		/**
		 * 开始调用远程方法
		 */
		protected function doCallOperation():AsyncToken{
			try{
				this.ws.requestTimeout = this.requestTimeoutSeconds;		
				var opr:AbstractOperation = this.ws.getOperation(operationName) as AbstractOperation;				
				opr.addEventListener(ResultEvent.RESULT, this.operationResultListener);
				opr.addEventListener(FaultEvent.FAULT, this.faultHandler);				
				opr.arguments = this.validateArgsInfo ? this.copyAndValidateArgs(operationArgs) : operationArgs;
				return opr.send();
			}catch(e:Error){				
				trace(e);	
				this.endFaildCall(null, null, e);
			}
			return null;
		}
		
		
		/**
		 * copy the given args and validate them
		 */
		protected function copyAndValidateArgs(args:Array):Array{
			var copied:Array = new Array();
			for each(var obj:Object in args){
				// deal with user defined object
				if(obj is ICloneable){
					obj = ICloneable(obj).clone();
					if(obj is IRpcBean){
						IRpcBean(obj).encode4Rpc();
					}
				}
				// deal with Number object witch is NaN
				else if((obj is Number) && isNaN(Number(obj))){
					obj = 0;
				}
				// we can't detect null Date and null String now
				
				copied.push(obj);
			}
			return copied;
		}	


		override protected function dispatchRpcResultEvent(e:RpcEvent):void{
			super.dispatchRpcResultEvent(e);
			
			var ee:WsEvent = new WsEvent(WsEvent.RESULT);
			ee.copyProperties(e);
			dispatchEvent(ee);
		}		
		
		override protected function dispatchRpcFaultEvent(e:RpcEvent):void{
			super.dispatchRpcFaultEvent(e);
			
			var ee:WsEvent = new WsEvent(WsEvent.FAULT);
			ee.copyProperties(e);
			dispatchEvent(ee);
		}
		
	}
}
